/**
 * Copyright 2009 Henning Teek, Ingo Renner
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.bricket.web;

import java.io.InputStream;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import javax.jcr.ImportUUIDBehavior;
import javax.servlet.http.HttpServletRequest;

import org.apache.wicket.Component;
import org.apache.wicket.Page;
import org.apache.wicket.Request;
import org.apache.wicket.ResourceReference;
import org.apache.wicket.Response;
import org.apache.wicket.Session;
import org.apache.wicket.Component.IVisitor;
import org.apache.wicket.authentication.AuthenticatedWebSession;
import org.apache.wicket.extensions.ajax.markup.html.form.upload.UploadWebRequest;
import org.apache.wicket.markup.html.WebPage;
import org.apache.wicket.protocol.http.WebRequest;
import org.apache.wicket.protocol.http.WebRequestCycle;
import org.apache.wicket.request.IRequestCycleProcessor;
import org.apache.wicket.spring.injection.annot.SpringComponentInjector;
import org.odlabs.wiquery.core.commons.WiQueryInstantiationListener;
import org.odlabs.wiquery.ui.themes.IThemableApplication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import brix.Brix;
import brix.BrixNodeModel;
import brix.Path;
import brix.config.BrixConfig;
import brix.config.PrefixUriMapper;
import brix.config.UriMapper;
import brix.jcr.ThreadLocalSessionFactory;
import brix.jcr.api.JcrSession;
import brix.plugin.publishing.PublishingPlugin;
import brix.plugin.site.SitePlugin;
import brix.web.BrixRequestCycleProcessor;
import brix.web.nodepage.BrixNodePageUrlCodingStrategy;
import brix.workspace.Workspace;
import brix.workspace.WorkspaceManager;

import com.googlecode.bricket.repository.BricketService;
import com.googlecode.bricket.web.auth.LoginPage;
import com.googlecode.bricket.web.brixadmin.AdminPage;

public class WicketApplication extends AbstractWicketApplication implements BrixAwareApplication, IThemableApplication {
	private final static Logger log = LoggerFactory.getLogger(WicketApplication.class);

	private Brix brix;

	public static final String BRIX_SITE = "/brix:root/brix:web/brix:site";

	/**
	 * The wiquery listener used to manage WiQuery components
	 */
	private WiQueryInstantiationListener wiqueryPluginInstantiationListener;

	@Override
	protected void init() {
		// we add a component instantiation listener to create plugin managers
		// each time a plugin is created
		wiqueryPluginInstantiationListener = new WiQueryInstantiationListener();
		addComponentInstantiationListener(wiqueryPluginInstantiationListener);

		super.init();
		addComponentInstantiationListener(new SpringComponentInjector(this));

		final ThreadLocalSessionFactory sf = getJcrSessionFactory();
		final WorkspaceManager wm = getWorkspaceManager();

		try {
			// create uri mapper for the cms
			// we are mounting the cms on the root, and getting the workspace
			// name from the
			// application properties
			UriMapper mapper = new PrefixUriMapper(Path.ROOT) {
				public Workspace getWorkspaceForRequest(WebRequestCycle requestCycle, Brix brix) {
					final String name = getProperties().getJcrDefaultWorkspace();
					final SitePlugin sitePlugin = SitePlugin.get(brix);
					return sitePlugin.getSiteWorkspace(name, PublishingPlugin.STATE_PRODUCTION);
				}
			};

			// create brix configuration
			final BrixConfig config = new BrixConfig(sf, wm, mapper);
			config.setHttpPort(getProperties().getHttpPort());
			config.setHttpsPort(getProperties().getHttpsPort());

			// register bricket plugins from spring context
			Map<String, BricketPlugin> bricketPlugins = getApplicationContext().getBeansOfType(BricketPlugin.class);
			log.info("bricket plugins = " + (bricketPlugins == null ? "null" : bricketPlugins.keySet()));

			for (BricketPlugin bricketPlugin : bricketPlugins.values()) {
				bricketPlugin.register(config.getRegistry());
			}

			// create brix instance and attach it to this application
			brix = getWicketBrix(config);
			brix.attachTo(this);
			initializeRepository();
			String[] statesForWorkspaces = new String[] {
					PublishingPlugin.STATE_DEVELOPMENT,
					PublishingPlugin.STATE_STAGING,
					PublishingPlugin.STATE_PRODUCTION
			};
			for (String state : statesForWorkspaces) {
				initDefaultWorkspace(state);
			}
		} catch (Exception e) {
			log.error("Exception in WicketApplication init()", e);
		} finally {
			// since we accessed session factory we also have to perform cleanup
			cleanupSessionFactory();
		}

		// mount admin page
		mount(new QueryStringHybridUrlCodingStrategy("/admin", AdminPage.class));

		getApplicationSettings().setAccessDeniedPage(LoginPage.class);

		// initialize bricket services from spring context
		Map<String, BricketService> bricketServices = getApplicationContext().getBeansOfType(BricketService.class);
		log.info("bricket services = " + (bricketServices == null ? "null" : bricketServices.keySet()));

		for (BricketService bricketService : bricketServices.values()) {
			bricketService.init();
		}
	}

	protected WicketBrix getWicketBrix(BrixConfig config) {
		return new WicketBrix(config);
	}

	public Brix getBrix() {
		return brix;
	}

	private void initDefaultWorkspace(String state) {
		try {
			final String defaultState = state;
			final String wn = getProperties().getJcrDefaultWorkspace();
			final SitePlugin sp = SitePlugin.get(brix);

			if (!sp.siteExists(wn, defaultState)) {
				Workspace w = sp.createSite(wn, defaultState);
				JcrSession session = brix.getCurrentSession(w.getId());

				session.importXML("/", getWorkspaceXml(),
						ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);

				brix.initWorkspace(w, session);
				session.save();
			}
		} catch (Exception e) {
			throw new RuntimeException("Could not initialize jackrabbit workspace with Brix", e);
		}
	}

	protected InputStream getWorkspaceXml() {
		return getClass().getResourceAsStream("workspace.xml");
	}

	/**
	 * Allow Brix to perform repository initialization
	 */
	private void initializeRepository() {
		try {
			brix.initRepository();
		} finally {
			// cleanup any sessions we might have created
			cleanupSessionFactory();
		}
	}

	@Override
	public BrixNodeModel getBrixLink(String path) {
		return new BrixNodeModel(BRIX_SITE + path, getWorkspaceId());
	}

	@Override
	public BrixNodeModel getI18nBrixLink(Session session, String path) {
		return new BrixNodeModel(BRIX_SITE + ((WicketWebSession) session).getI18nLink(path), getWorkspaceId());
	}

	@Override
	protected IRequestCycleProcessor newRequestCycleProcessor() {
		/*
		 * install brix request cycle processor
		 *
		 * this will allow brix to take over part of wicket's url space and
		 * handle requests
		 */
		return new BrixRequestCycleProcessor(brix);
	}

	@Override
	public Class<? extends Page> getHomePage() {
		/*
		 * use special class so that the URL coding strategy knows we want to go home
		 * it is not possible to just return null here because some pages (e.g. expired page)
		 * rely on knowing the home page
		 */
		return BrixNodePageUrlCodingStrategy.HomePage.class;
	}

	@Override
	public Session newSession(Request request, Response response) {
		return new WicketWebSession(request);
	}

	@Override
	protected Class<? extends WebPage> getSignInPageClass() {
		return LoginPage.class;
	}

	@Override
	protected Class<? extends AuthenticatedWebSession> getWebSessionClass() {
		return WicketWebSession.class;
	}

	@Override
	protected WebRequest newWebRequest(HttpServletRequest servletRequest) {
		return new UploadWebRequest(servletRequest);
	}

	/**
	 * Search the FeedbackPanel on the given page.
	 * @param page
	 * @return null or FeedbackPanel
	 */
	public BricketFeedbackPanel getBricketFeedbackPanel(Page page) {
		return (BricketFeedbackPanel) page.visitChildren(new IVisitor<Component>() {
			private final Set<Component> visited = new HashSet<Component>();

			@Override
			public Object component(Component component) {
				if (!visited.contains(component)) {
					visited.add(component);
					if (component instanceof BricketFeedbackPanel) {
						return component;
					}
				}
				return IVisitor.CONTINUE_TRAVERSAL;
			}
		});
	}

	@Override
	public ResourceReference getTheme(Session session) {
		return new ResourceReference(WicketApplication.class, "theme.css");
	}
}
